// Copyright 2012 dmitry.a.novikov@gmail.com
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "consistenz.h"
#include "objectimpl.h"
#include "filer.h"
#include "valuearray.h"
#include "dbobject.h"
#include "domain.h"

namespace CnsZ {

  DEFINE_CNSZ_CLASS(Stream, Object, NULL_FACTORY);

  DEFINE_CNSZ_CLASS(Filer, Object, NULL_FACTORY);

  class FlatMemoryStream : public Stream {
  protected:
    Byte* buffer;
    size_t pagesize;
    size_t pagepos;

    FlatMemoryStream() : buffer(0), pagesize(0), pagepos(0) {}

    virtual void endOfReadBuffer() {
      throw EndOfFile();
    }

    virtual void endOfWriteBuffer() {
      throw EndOfFile();
    }

  public:
    DECLARE_CNSZ_CLASS(FlatMemoryStream);
    
    Stream* init(Byte* buffer, size_t pagesize) {
      this->buffer = buffer;
      this->pagesize = pagesize;
      this->pagepos = 0;
      return this;
    }

    void wrBytes(size_t num, const void* bytes) {
      const Byte* src = (const Byte*)bytes;
      do {
        if(pagepos==pagesize) {
          endOfWriteBuffer();
        }
        while(num && pagepos < pagesize) {
          buffer[pagepos++] = *src++;
          --num;
        }
      }
      while(num);
    }

    void wrBytes(size_t num, Stream* strm) {
      do {
        if(buffer==0 || pagepos==pagesize) {
          endOfWriteBuffer();
        }
        size_t n = min(num, pagesize-pagepos);
        strm->rdBytes(n, buffer+pagepos);
        pagepos += n;
        num -= n;
      }
      while(num);
    }

    void rdBytes(size_t num, void* res) {
      Byte* resbuf = (Byte*)res;
      do {
        if(pagepos==pagesize) {
          endOfReadBuffer();
        }
        while(num && pagepos < pagesize) {
          *resbuf++ = buffer[pagepos++];
          --num;
        }
      }
      while(num);
    }

    size_t forward(size_t offset) {
      pagepos+=offset;
      if(pagepos>pagesize) {
        throw EndOfFile();
      }
      return pagepos;
    }

    size_t back(size_t offset) {
      if(pagepos<offset) {
        throw EndOfFile();
      }
      return pagepos-=offset;
    }

    void seek(size_t pos) {
      if(pos>pagesize) {
        throw EndOfFile();
      }
      pagepos=pos;
    }
    
    size_t tell() const {
      return pagepos;
    }
  };

  class MemoryPagesStream : public FlatMemoryStream {
    A<A<UInt8> > pages;
    size_t nextpage;
    size_t overallpos;
  protected:
    MemoryPagesStream() : nextpage(0), overallpos(0) {
    }

    virtual void endOfReadBuffer() {
      if(nextpage<pages.size()) {
        setpagecur(pages[nextpage++]);
      }
      else {
        throw EndOfFile();
      }
    }

    void setpagecur(A<UInt8>& page, size_t curp = 0) {
      FlatMemoryStream::buffer = page.begin();
      FlatMemoryStream::pagepos = curp;
    }

    virtual void endOfWriteBuffer() {
      if(nextpage==pages.size()) {
        pages.push_back(A<UInt8>(pagesize));
        setpagecur(pages.back());
      }
      else {
        setpagecur(pages[nextpage]);
      }
      ++nextpage;
    }
  public:
    DECLARE_CNSZ_CLASS(MemoryPagesStream);

    MemoryPagesStream* init(size_t pagesize) {
      this->pagesize = pagesize;
      this->nextpage = 0;
      return this;
    }

    void wrBytes(size_t num, const void* bytes) {
      FlatMemoryStream::wrBytes(num, bytes);
      overallpos+=num;
    }

    void wrBytes(size_t num, Stream* strm) {
      FlatMemoryStream::wrBytes(num, strm);
      overallpos+=num;
    }

    void rdBytes(size_t num, void* res) {
      FlatMemoryStream::rdBytes(num, res);
      overallpos+=num;
    }

    size_t forward(size_t offset) {
      seek(overallpos+offset);
      return overallpos;
    }

    size_t back(size_t offset) {
      if(offset>overallpos) {
        throw InvalidArg(String(L""));
      }
      seek(overallpos-offset);
      return overallpos;
    }

    void seek(size_t position) {
      size_t numpages = pages.size();
      size_t page = position/pagesize;
      nextpage = page; ++nextpage;
      if(numpages<nextpage) {
        pages.resize(nextpage, numpages==0 ? A<UInt8>(pagesize) : pages.front());
      }
      setpagecur(pages[page], position%pagesize);
      if(position > pagesize) {
        pagesize = position;
      }
      overallpos = position;
    }
  };

  P<Stream> Stream::NewMemoryPages(size_t pagesize) {
    return ImplRefCount<MemoryPagesStream>::New()->init(pagesize);
  }

  P<Stream> Stream::NewFlatMemory(Byte* buffer, size_t buffersize) {
    return ImplRefCount<FlatMemoryStream>::New()->init(buffer, buffersize);
  }

  DEFINE_CNSZ_CLASS(FlatMemoryStream, Stream, NULL_FACTORY);
                    
  DEFINE_CNSZ_CLASS(MemoryPagesStream, Stream, NULL_FACTORY);


  class CopyFilerImpl : public CopyFiler {
    P<Stream> strm;
  public:
    CopyFiler* init(Stream* strm) {
      this->strm = strm;
      return this;
    }
    
    void setStream(Stream* strm) {
      this->strm = strm;
    }
    
    Stream& stream() { return *strm; }

    void wrInt8(Int8 val) {
      strm->wrBytes(sizeof(val), &val);
    }
    Int8 rdInt8() {
      Int8 val;
      strm->rdBytes(sizeof(val), &val);
      return val;
    }

    void wrInt16(Int16 val) {
      strm->wrBytes(sizeof(val), &val);
    }
    Int16 rdInt16() {
      Int16 val;
      strm->rdBytes(sizeof(val), &val);
      return val;
    }

    void wrInt32(Int32 val) {
      strm->wrBytes(sizeof(val), &val);
    }

    Int32 rdInt32() {
      Int32 val;
      strm->rdBytes(sizeof(val), &val);
      return val;
    }

    void wrInt64(Int64 val) {
      strm->wrBytes(sizeof(val), &val);
    }
    Int64 rdInt64() {
      Int64 val;
      strm->rdBytes(sizeof(val), &val);
      return val;
    }

    void wrDouble(Double val) {
      strm->wrBytes(sizeof(val), &val);
    }
    Double rdDouble() {
      Double val;
      strm->rdBytes(sizeof(val), &val);
      return val;
    }

    void wrFloat(Float val) {
      strm->wrBytes(sizeof(val), &val);
    }
    Float rdFloat() {
      Int16 val;
      strm->rdBytes(sizeof(val), &val);
      return val;
    }

    void wrObjectPtr(const Object* val) {
      strm->wrBytes(sizeof(val), &val);
    }
    P<Object> rdObjectPtr() {
      Object* val;
      strm->rdBytes(sizeof(val), &val);
      return val;
    }

    void wrObject(const Object* val) {
      wrObjectPtr(val);
    }
    P<Object> rdObject() {
      return rdObjectPtr();
    }

    void wrHandle(const Handle* handle) {
      wrObjectPtr(handle);
    }
    P<Handle> rdHandle() {
      return castTo<Handle>(rdObjectPtr().get());
    }
  };

  P<Filer> CopyFiler::New(Stream* stream) {
    return ImplRefCount<CopyFilerImpl>::New()->init(stream);
  }

  class PackingFilerImpl : public CopyFiler {
    P<Stream> strm;
  public:
    CopyFiler* init(Stream* strm) {
      this->strm = strm;
      return this;
    }

    enum {
      ShortExpMask_0x07 = 0x07,

      SignBit_0x80      = 0x80,
      LongExpBit_0x40   = 0x40,
      HasMantissa_0x20  = 0x20,
    };
    union IEEEDoubleBitsLayout {
      struct {
        UInt64 mantissa:52;
        UInt64 exp:11;
        UInt64 sign:1;
      } bits;
      Double value;
      Byte bytes[8];
    };

    void wrPackedDouble(Double val) {
      IEEEDoubleBitsLayout& data = *((IEEEDoubleBitsLayout*)&val);
      if(data.bits.exp==0 && data.bits.mantissa==0) {
        strm->wrBytes(1, &data.bytes+1);
      }
      else {
        size_t i=0;
        Byte bytes[9];
        *bytes = Byte(data.bits.sign==1 ? SignBit_0x80 : 0);
        int exp = int(data.bits.exp)-1023;
        if(-7 <= exp && exp <= 7) {
          *bytes |= (UInt8(exp) & ShortExpMask_0x07);
        } else {
          *bytes |= LongExpBit_0x40;
          ++i;
          bytes[1] = Byte(UInt16(exp) & 0xFF);
          *bytes |= ((UInt16(exp) >> 8) & ShortExpMask_0x07);
        }
        const UInt64& mantissa = data.bits.mantissa;
        if(mantissa) {
          *bytes |= HasMantissa_0x20;
          ++i;
          int last = 0;
          int cur = 7;
          while(data.bytes[last]==0) {
            ++last;
          }
          bytes[i++] = Byte((cur-last) << 4) | data.bytes[last] & 0x0F;
          while(--cur!=last) {
            bytes[i++] = data.bytes[cur];
          }
        }
      }
    }

    Double rdPackedDouble() {
      return 0.;
    }

    void wrBase128PackedInteger(Int64 val) {
      size_t n=0;
      Byte bytes[9];
      bool negative = (val < 0);
      if (negative) {
        val=-val;
      }
      for(;;) {
        bytes[n] = Byte(val & 0x7F);
        val >>= 7;
        if(!val) {
          bytes[n++] |= (negative ? 0xC0 : 0x80);
          break;
        }
        ++n;
      }
      strm->wrBytes(n, bytes);
    }

    Int64 rdBase128PackedInteger() {
      size_t n=0;
      Int64 res = 0;
      Byte byte;
      strm->rdBytes(1, &byte);
      res |= (byte & 0x3F);
      bool negative = getbit(byte, 0x40);
      ++n;
      while((byte & 0x80)==0)  {
        res <<= 7;
        strm->rdBytes(1, &byte);
        res |= (byte & 0x7F);
        ++n;
      }
      if(negative) {
        res = -res;
      }
      return res;
    }

    void setStream(Stream* strm) {
      this->strm = strm;
    }
    
    Stream& stream() { return *strm; }

    void wrInt8(Int8 val) {
      strm->wrBytes(sizeof(val), &val);
    }
    Int8 rdInt8() {
      Int8 val;
      strm->rdBytes(sizeof(val), &val);
      return val;
    }

    void wrInt16(Int16 val) {
      wrBase128PackedInteger(val);
    }

    Int16 rdInt16() {
      return Int16(rdBase128PackedInteger());
    }

    void wrInt32(Int32 val) {
      wrBase128PackedInteger(val);
    }

    Int32 rdInt32() {
      return Int32(rdBase128PackedInteger());
    }

    void wrInt64(Int64 val) {
      wrBase128PackedInteger(val);
    }
    Int64 rdInt64() {
      return rdBase128PackedInteger();
    }

    void wrDouble(Double val) {
      wrPackedDouble(val);
    }

    Double rdDouble() {
      return rdPackedDouble();
    }

    void wrFloat(Float val) {
      strm->wrBytes(sizeof(val), &val);
    }
    Float rdFloat() {
      Int16 val;
      strm->rdBytes(sizeof(val), &val);
      return val;
    }

    void wrObjectPtr(const Object* val) {
      strm->wrBytes(sizeof(val), &val);
    }
    P<Object> rdObjectPtr() {
      Object* val;
      strm->rdBytes(sizeof(val), &val);
      return val;
    }

    void wrObject(const Object* val) {
      wrObjectPtr(val);
    }
    P<Object> rdObject() {
      return rdObjectPtr();
    }

    void wrHandle(const Handle* handle) {
      wrObjectPtr(handle);
    }
    P<Handle> rdHandle() {
      return castTo<Handle>(rdObjectPtr().get());
    }
  };

  DEFINE_CNSZ_CLASS(CopyFiler, Filer, IMPL_FACTORY);

}//namespace CnsZ
